/*
 * Copyright (c) 2009 Mikolaj Golub
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/*
 * routines for pkvmdumps.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <unistd.h>
#include <strings.h>

#include <kvm.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <machine/minidump.h>
#include <limits.h>

#include "kvm_private.h"
#include "pkvmdump.h"

SLIST_HEAD(emaphead, entry);
			   
struct entry {
	SLIST_ENTRY(entry)		entries;
	struct pkvmdump_entryhdr	ehdr;
	off_t				offset; /* offset to data in dump */
};

/* minidump must be the first item! */
struct vmstate {
	int			minidump;		/* 1 = minidump mode */
	struct minidumphdr	hdr;
	struct emaphead		*emaphead;
};

void
_kvm_pkvmdump_freevtop(kvm_t *kd)
{
	struct entry	*ep, *p;
	while (!SLIST_EMPTY(kd->vmst->emaphead)) {
		ep = SLIST_FIRST(kd->vmst->emaphead);
		SLIST_REMOVE_HEAD(kd->vmst->emaphead, entries);
		free(ep);
	}
	free(kd->vmst->emaphead);
	kd->vmst->emaphead = NULL;
	
	free(kd->vmst);
	kd->vmst = NULL;
}

int
_kvm_pkvmdump_initvtop(kvm_t *kd)
{
	struct vmstate	*vmst;
	int		fd, n;
	off_t		off;
	struct emaphead			*emaphead;
	struct pkvmdump_entryhdr	ehdr;

	vmst = _kvm_malloc(kd, sizeof(*vmst));
	if (vmst == 0) {
		_kvm_err(kd, kd->program, "cannot allocate vm");
		return (-1);
	}
	kd->vmst = vmst;
	bzero(vmst, sizeof(*vmst));
	vmst->minidump = 2;

	if (pread(kd->pmfd, &vmst->hdr, sizeof(vmst->hdr), 0) !=
	    sizeof(vmst->hdr)) {
		_kvm_err(kd, kd->program, "cannot read dump header");
		return (-1);
	}
	if (strncmp(PKVMDUMP_MAGIC, vmst->hdr.magic, sizeof(vmst->hdr.magic)) != 0) {
		_kvm_err(kd, kd->program, "not a pkvmdump");
		return (-1);
	}
	if (vmst->hdr.version != PKVMDUMP_VERSION) {
		_kvm_err(kd, kd->program, "wrong pkvmdump version. expected %d got %d",
		    PKVMDUMP_VERSION, vmst->hdr.version);
		return (-1);
	}

	vmst->emaphead = _kvm_malloc(kd, sizeof(*(vmst->emaphead)));
	if (vmst->emaphead == 0) {
		_kvm_err(kd, kd->program, "cannot allocate vm");
		return (-1);
	}
	SLIST_INIT(vmst->emaphead);

	if (lseek (kd->pmfd, sizeof(vmst->hdr), SEEK_SET) < 0) {
		_kvm_err(kd, kd->program, "cannot seek in dump file");
		return (-1);
	}

	off = sizeof(vmst->hdr);

	while ((n = read(kd->pmfd, &ehdr, sizeof(ehdr))) != 0) {		
		struct entry	*ep, *prev, *next;
		
		if (n != sizeof(ehdr)) {
			_kvm_err(kd, kd->program, "cannot read next entry header");
			return (-1);
		}

		off += n;

		ep = _kvm_malloc(kd, sizeof(*ep));
		if (ep == 0) {
			_kvm_err(kd, kd->program, "cannot allocate vm");
			return (-1);
		}		
		ep->ehdr = ehdr;
		ep->offset = off;
		
		/*
		 * Insert to the list in ascending order.
		 * We want the list sorted to make lookup faster
		 */

		//SLIST_INSERT_HEAD(vmst->emaphead, ep, entries);
		
#define LESSEQ(e1, e2) ((e1->ehdr.addr < e2->ehdr.addr) ||		\
		      ((e1->ehdr.addr == e2->ehdr.addr) &&		\
		       (e1->ehdr.size >= e2->ehdr.size)))
		prev = SLIST_FIRST(vmst->emaphead);
		if(!prev || LESSEQ(ep, prev))
			SLIST_INSERT_HEAD(vmst->emaphead, ep, entries);
		else {			
			while (next = SLIST_NEXT(prev, entries))
				if (LESSEQ(ep, next))
					break;
				else
					prev = next;
			SLIST_INSERT_AFTER(prev, ep, entries);
		}
#undef LESSEQ
		
		if (lseek (kd->pmfd, ehdr.size, SEEK_CUR) < 0) {
			_kvm_err(kd, kd->program, "cannot seek in dump file");
			return (-1);
		}

		off += ehdr.size;
	}

	return (0);
}

static int
_kvm_pkvmdump_vatop(kvm_t *kd, u_long va, off_t *pa)
{
	struct entry	*ep, *best;
	u_long		ret;
	
	*pa = 0;
	best = NULL;
	SLIST_FOREACH(ep, kd->vmst->emaphead, entries) {
		if (va < ep->ehdr.addr) /* all other elements are "greater" */
			break;		
		if ((va >= ep->ehdr.addr) &&
		    (va < (ep->ehdr.addr + ep->ehdr.size)))
			if ((best == NULL) ||
			    ((ep->ehdr.size - (va - ep->ehdr.addr)) > ret)) {
				best = ep;
				ret = ep->ehdr.size - (va - ep->ehdr.addr);
			}
	}

	if (best != NULL) {
		*pa = best->offset + (va - best->ehdr.addr);
		return ((int) ret);
	} else {
		_kvm_err(kd, 0, "invalid address (0x%lx)", va);
		return (0);
	}
}

int
_kvm_pkvmdump_kvatop(kvm_t *kd, u_long va, off_t *pa)
{

	if (ISALIVE(kd)) {
		_kvm_err(kd, 0, "kvm_kvatop called in live kernel!");
		return (0);
	} else
		return (_kvm_pkvmdump_vatop(kd, va, pa));
}
